

package com.guang.springbootexecutordistributedlock.distributedlock.db.biz;

import java.util.Date;


import com.guang.springbootexecutordistributedlock.dao.DistributedLockRecordMapper;
import com.guang.springbootexecutordistributedlock.dao.entity.DistributedLockRecord;
import com.guang.springbootexecutordistributedlock.dao.entity.DistributedLockRecordExample;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class DistributedLockRecordBiz {
    private static final Logger LOGGER = LoggerFactory.getLogger(DistributedLockRecordBiz.class);
    @Value("${liguang.ip}")
    private String ip;
    @Value("${server.port}")
    private Integer port;

    @Resource
    private DistributedLockRecordMapper lockRecordMapper;

    public DistributedLockRecordBiz() {
    }

    public boolean isAlreadyHoldLock(String lockKey, String uuid) {
        DistributedLockRecord lock = this.lockRecordMapper.selectByPrimaryKey(lockKey);
        if (lock == null) {
            return false;
        } else {
            return this.isLockExpired(lock) ? false : this.isAlreadyHoldLock(lockKey, uuid, lock);
        }
    }

    public boolean tryLock(String lockKey, String uuid, long lockTimeOut) {
        DistributedLockRecord lock = this.lockRecordMapper.selectByPrimaryKey(lockKey);
        if (lock == null) {
            try {
                this.lockRecordMapper.insert(this.buildInitDistributedLock(lockKey, uuid, lockTimeOut));
                return true;
            } catch (Exception e) {
                LOGGER.error("重复键插入异常");
                return false;
            }
        } else if (this.isLockExpired(lock)) {
            LOGGER.error("lockKey:{}, uuid:{}, ip:{}, port:{}, thread:{}, 在持有锁期间，不应出现锁超时", new Object[]{lock.getLockKey(), lock.getUuid(), lock.getMachineIp(), lock.getMachinePort(), lock.getThreadName()});
            return this.updateDistributedLock(lockKey, uuid, lockTimeOut, lock);
        } else {
            return this.isAlreadyHoldLock(lockKey, uuid, lock);
        }
    }

    public void refreshDistributedLock(String lockKey, String uuid, long lockTimeOut) {
        DistributedLockRecord lock = this.lockRecordMapper.selectByPrimaryKey(lockKey);
        if (lock == null) {
            throw new RuntimeException("lockKey:" + lockKey + ", uuid:" + uuid + ", ip:" + this.ip + ", port:" + this.port + ", thread:" + Thread.currentThread().getName() + ", 应已持有数据库分布式锁，但无锁记录");
        } else if (this.isLockExpired(lock)) {
            throw new RuntimeException("lockKey:" + lockKey + ", uuid:" + uuid + ", ip:" + this.ip + ", port:" + this.port + ", thread:" + Thread.currentThread().getName() + ", 应已持有数据库分布式锁，但锁记录已超时");
        } else if (!this.isAlreadyHoldLock(lockKey, uuid, lock)) {
            throw new RuntimeException("lockKey:" + lockKey + ", uuid:" + uuid + ", ip:" + this.ip + ", port:" + this.port + ", thread:" + Thread.currentThread().getName() + ", 应已持有数据库分布式锁，但锁记录并非本机器本线程");
        } else if (!this.refreshDistributedLock(lockKey, uuid, lockTimeOut, lock)) {
            throw new RuntimeException("lockKey:" + lockKey + ", uuid:" + uuid + "ip:" + this.ip + ", port:" + this.port + ", thread:" + Thread.currentThread().getName() + ", 延续超时时间失败");
        }
    }

    public boolean removeDistributedLockIfHoldLock(String lockKey, String uuid) {
        DistributedLockRecordExample example = new DistributedLockRecordExample();
        DistributedLockRecordExample.Criteria criteria = example.createCriteria();
        criteria.andLockKeyEqualTo(lockKey).andUuidEqualTo(uuid).andMachineIpEqualTo(this.ip).andMachinePortEqualTo(String.valueOf(this.port)).andThreadNameEqualTo(Thread.currentThread().getName());
        return this.lockRecordMapper.deleteByExample(example) == 1;
    }

    private boolean refreshDistributedLock(String lockKey, String uuid, long lockTimeOut, DistributedLockRecord record) {
        DistributedLockRecordExample example = new DistributedLockRecordExample();
        DistributedLockRecordExample.Criteria criteria = example.createCriteria();
        criteria.andLockKeyEqualTo(lockKey).andUuidEqualTo(uuid).andMachineIpEqualTo(this.ip).andMachinePortEqualTo(String.valueOf(this.port)).andThreadNameEqualTo(Thread.currentThread().getName()).andVersionEqualTo(record.getVersion());
        return this.lockRecordMapper.updateByExampleSelective(this.buildRefreshDistributedLock(lockKey, uuid, lockTimeOut, record), example) == 1;
    }

    private boolean updateDistributedLock(String lockKey, String uuid, long lockTimeOut, DistributedLockRecord record) {
        DistributedLockRecordExample example = new DistributedLockRecordExample();
        DistributedLockRecordExample.Criteria criteria = example.createCriteria();
        criteria.andLockKeyEqualTo(lockKey).andVersionEqualTo(record.getVersion());
        return this.lockRecordMapper.updateByExampleSelective(this.buildUpdateDistributedLock(lockKey, uuid, lockTimeOut, record), example) == 1;
    }

    private boolean isLockExpired(DistributedLockRecord lock) {
        return (new Date()).compareTo(lock.getLockTimeOut()) >= 0;
    }

    private boolean isAlreadyHoldLock(String lockKey, String uuid, DistributedLockRecord record) {
        if (!lockKey.equals(record.getLockKey())) {
            return false;
        } else if (!uuid.equals(record.getUuid())) {
            return false;
        } else if (!this.ip.equals(record.getMachineIp())) {
            return false;
        } else if (this.port != Integer.parseInt(record.getMachinePort())) {
            return false;
        } else {
            return Thread.currentThread().getName().equals(record.getThreadName());
        }
    }

    private DistributedLockRecord buildInitDistributedLock(String lockKey, String uuid, long lockTimeOut) {
        DistributedLockRecord lock = new DistributedLockRecord();
        lock.setLockKey(lockKey);
        lock.setMachineIp(this.ip);
        lock.setMachinePort(String.valueOf(this.port));
        lock.setThreadName(Thread.currentThread().getName());
        lock.setLockTimeOut(new Date(System.currentTimeMillis() + lockTimeOut));
        lock.setUuid(uuid);
        lock.setVersion(1);
        return lock;
    }

    private DistributedLockRecord buildUpdateDistributedLock(String lockKey, String uuid, long lockTimeOut, DistributedLockRecord record) {
        DistributedLockRecord lock = new DistributedLockRecord();
        lock.setLockKey(lockKey);
        lock.setMachineIp(this.ip);
        lock.setMachinePort(String.valueOf(this.port));
        lock.setThreadName(Thread.currentThread().getName());
        lock.setLockTimeOut(new Date(System.currentTimeMillis() + lockTimeOut));
        lock.setUuid(uuid);
        lock.setVersion(record.getVersion() + 1);
        return lock;
    }

    private DistributedLockRecord buildRefreshDistributedLock(String lockKey, String uuid, long lockTimeOut, DistributedLockRecord record) {
        DistributedLockRecord lock = new DistributedLockRecord();
        lock.setLockKey(lockKey);
        lock.setMachineIp(this.ip);
        lock.setMachinePort(String.valueOf(this.port));
        lock.setThreadName(Thread.currentThread().getName());
        lock.setLockTimeOut(new Date(lock.getLockTimeOut().getTime() + lockTimeOut));
        lock.setUuid(uuid);
        lock.setVersion(record.getVersion() + 1);
        return lock;
    }
}
